Postgresql 的 ENUM 型態
可以用來取代小的屬性表.來看以下的例子
create table color (
  id int generated always as identity primary key
, name text
);
create table cars (
  brand text
, model text
, color integer references color(id)
);
insert into color(name) values
('blue'),('red'),('gray'),('black');
insert into cars(brand, model, color)
select brand, model, color.id
  from (values 
        ('ferari', 'testarosa', 'red'),
        ('aston martin', 'db2', 'blue'),
        ('bentley', 'mulsanne', 'gray'),
        ('ford', 'T', 'black')
       ) as data(brand, model, color)
  join color
    on color.name = data.color;
這時候在 cars 裡的值
select *
  from cars;
+--------------+-----------+-------+
|    brand     |   model   | color |
+--------------+-----------+-------+
| aston martin | db2       |     1 |
| ferari       | testarosa |     2 |
| bentley      | mulsanne  |     3 |
| ford         | T         |     4 |
+--------------+-----------+-------+
實際使用時,會跟 color 做 join, 取出 color.name
select brand
     , model
     , color.name as color
  from cars
  join color
    on color.id = cars.color;
+--------------+-----------+-------+
|    brand     |   model   | color |
+--------------+-----------+-------+
| aston martin | db2       | blue  |
| ferari       | testarosa | red   |
| bentley      | mulsanne  | gray  |
| ford         | T         | black |
+--------------+-----------+-------+
或是依據上面這道 SQL Command 建立一個 view, 方便使用.
在開發系統的過程中,諸如 color 這類的屬性表,不勝枚舉.
所以就有了這類的資料型態,ENUM, 可以翻譯為 枚舉.
來看怎樣建立 enum , 在 Postgresql 中,要先建立一個 enum 的 type,
在 MySQL 是在建立 table 時,直接用 enum . 好處是方便,但是不易於複用.
Postgresql 的方式,可以複用.
兩者各有千秋,也可以看出兩種資料庫產品的設計哲學差異.
create type color_t as enum('blue', 'red', 'gary', 'black');
drop table if exists cars;
create table cars (
  brand text
, model text
, color color_t
);
insert into cars(brand, model, color) values 
('ferari', 'testarosa', 'red'),
('aston martin', 'db2', 'blue'),
('bentley', 'mulsanne', 'gray'),
('ford', 'T', 'black');
ERROR:  22P02: invalid input value for enum color_t: "gray"
LINE 4: ('bentley', 'mulsanne', 'gray'),
                                ^
LOCATION:  enum_in, enum.c:60
出現錯誤了...因為前面建立 enum 時, gray 拼成 gary 了.
這樣就能看出 enum 的用處之一.
來看怎樣修正 enum
alter type color_t rename value 'gary' to 'gray';
這時候就能順利輸入了,同樣的 insert into ,我就不重複了.
來看 enum 另一個功用,指定排序順序.
不指定 order by 欄位時,隨機排列.
select *
  from cars;
+--------------+-----------+-------+
|    brand     |   model   | color |
+--------------+-----------+-------+
| ferari       | testarosa | red   |
| aston martin | db2       | blue  |
| bentley      | mulsanne  | gray  |
| ford         | T         | black |
+--------------+-----------+-------+
select *
  from cars
 order by color;
+--------------+-----------+-------+
|    brand     |   model   | color |
+--------------+-----------+-------+
| aston martin | db2       | blue  |
| ferari       | testarosa | red   |
| bentley      | mulsanne  | gray  |
| ford         | T         | black |
+--------------+-----------+-------+
這樣就依照 enum color_t 的順序排列了.
既然具有順序性(Ordering),那就可以查找某個值之前或之後.
select *
  from cars
 where color >= 'gray'
 order by color;
+---------+----------+-------+
|  brand  |  model   | color |
+---------+----------+-------+
| bentley | mulsanne | gray  |
| ford    | T        | black |
+---------+----------+-------+
(2 rows)
若要查找 enum 裡面的內容, 在 Postgresql 中有 Enum Support Functions
https://www.postgresql.org/docs/current/functions-enum.html
計有 enum_first() enum_last() enum_range(), 
enum_range(anyenum, anyenum) <- 找兩個enum 元素之間的值.
可以借助NULL搭配轉型.
select enum_first(null::color_t)
     , enum_first('black'::color_t) 
     , enum_last(null::color_t)
     , enum_range(null::color_t);
+------------+------------+-----------+-----------------------+
| enum_first | enum_first | enum_last |      enum_range       |
+------------+------------+-----------+-----------------------+
| blue       | blue       | black     | {blue,red,gray,black} |
+------------+------------+-----------+-----------------------+
當我們不知 enum 裡面任一元素時,使用 null 搭配轉型,可以取出 first, last, range.
當知道部分,可以使用以下方式取出區間
select enum_range(null, 'gray'::color_t)
     , enum_range('red'::color_t, null);
+-----------------+------------------+
|   enum_range    |    enum_range    |
+-----------------+------------------+
| {blue,red,gray} | {red,gray,black} |
+-----------------+------------------+
觀察 enum_range()  回傳的值,有{},應該是 array.
雖然文件中,沒有寫明是 array.我們可以使用 pg_typeof()
select pg_typeof(enum_range(null::color_t));
+-----------+
| pg_typeof |
+-----------+
| color_t[] |
+-----------+
是由 color_t 組成元素的 array. 
也可以利用系統表,看過之前一系列的介紹以後,應該能猜到是用 pg_enum.
我們先來建立另一個 enum ,方便探討.
create type dd_t as enum ('雪風','初風','天津風','時津風');
可以使用以下 SQL Command
select enumtypid::regtype
     , array_agg(enumsortorder::text || '->' || enumlabel) as elements
  from pg_enum
 group by enumtypid;
 
+-----------+---------------------------------------+
| enumtypid |               elements                |
+-----------+---------------------------------------+
| color_t   | {1->blue,2->red,4->black,3->gray}     |
| dd_t      | {1->雪風,2->初風,3->天津風,4->時津風} |
+-----------+---------------------------------------+
(2 rows)
或是不使用轉型,由 join pg_type 取出 enum_name 
select type.typname as enum_name
     , string_agg(enum.enumlabel, '|') as elem1
     , array_agg(enum.enumsortorder::text || '->' || enum.enumlabel) as elem2
  from pg_enum as enum
  join pg_type as type
    on (type.oid = enum.enumtypid)
 group by type.typname;
+-----------+-------------------------+---------------------------------------+
| enum_name |          elem1          |                 elem2                 |
+-----------+-------------------------+---------------------------------------+
| color_t   | blue|red|black|gray     | {1->blue,2->red,4->black,3->gray}     |
| dd_t      | 雪風|初風|天津風|時津風    | {1->雪風,2->初風,3->天津風,4->時津風}     |
+-----------+-------------------------+---------------------------------------+
(2 rows)
上面有驗證了 enum_range() 回傳了 array.
可以透過 array 的 unnest(), 搭配 enum_range() 
select *
  from unnest(enum_range(null::dd_t)) 
       with ordinality as un(rn, elem);
+--------+------+
|   rn   | elem |
+--------+------+
| 雪風   |    1 |
| 初風   |    2 |
| 天津風 |    3 |
| 時津風 |    4 |
+--------+------+
(4 rows)
當然比較簡單的方式.是直接 unnest()
select unnest(enum_range(null::dd_t));
+--------+
| unnest |
+--------+
| 雪風   |
| 初風   |
| 天津風 |
| 時津風 |
+--------+
(4 rows)
可以觀察到 array 型態在 Postgresql 中,應用廣泛.
查元素是否存在 table 的 enum 型態欄位裡,可以使用轉型
select *
  from cars
 where cars.color::text = 'white';
+-------+-------+-------+
| brand | model | color |
+-------+-------+-------+
+-------+-------+-------+
但是 white 是不能轉型為 color_t 的.
直接查看 enum 的方法
例如 島風 是否有在水雷戰隊裡面.
select '島風' = any(enum_range(null::dd_t)::text[]);
+----------+
| ?column? |
+----------+
| f        |
+----------+
也是轉型成 text[],來比較.來看天津風
select '天津風' = any(enum_range(null::dd_t)::text[]);
+----------+
| ?column? |
+----------+
| t        |
+----------+
接著來看加入元素的方法,來把島風加入水雷戰隊.
alter type dd_t add value '島風' after '天津風';
ERROR:  25001: ALTER TYPE ... ADD cannot run inside a transaction block
LOCATION:  PreventInTransactionBlock, xact.c:3213
Time: 12.060 ms
這樣會發生錯誤,雖然官網文件上寫可以使用 before / after , 但也有 transaction 的限制.
這樣島風就不能在好朋友天津風旁邊了.
我們先來將島風加到最後看看
alter type dd_t add value '島風' after '時津風';
ERROR:  25001: ALTER TYPE ... ADD cannot run inside a transaction block
LOCATION:  PreventInTransactionBlock, xact.c:3213
一樣是不行...
修改 pg_enum 直接加進去看看,這種奇技淫巧,我們先來做select, 然後再 insert
select 'dd_t'::regtype::oid
     , '島風'
     , (select max(enumsortorder) + 1 
          from pg_enum
         where enumtypid = 'dd_t'::regtype
       );
+-------+----------+----------+
|  oid  | ?column? | ?column? |
+-------+----------+----------+
| 34682 | 島風     |        5 |
+-------+----------+----------+
insert into pg_enum(enumtypid, enumlabel, enumsortorder)
select 'dd_t'::regtype::oid
     , '島風'
     , (select max(enumsortorder) + 1 
          from pg_enum
         where enumtypid = 'dd_t'::regtype
       );
select unnest(enum_range(null::dd_t));
+--------+
| unnest |
+--------+
| 雪風   |
| 初風   |
| 天津風 |
| 時津風 |
| 島風   |
+--------+
(5 rows)
島風已經加入了.這種方式適合有愛的提督.
一般還是會選擇 建立新的 enum, 
然後將有使用到 enum 的 table 修改成使用新 enum的方式.